package org.eclipse.swt.snippets;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.nio.channels.FileChannel;
import java.util.Date;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LogWatcher implements Runnable {

    private boolean                     exit;
    private boolean                     skip;
    private LinkedList<MessageListener> listeners;
    private Pattern                     pattern;
    private File                        file;
    private long                        startLine;

    public LogWatcher() {
        listeners = new LinkedList<MessageListener>();
        pattern = Pattern.compile("(.{23}) (.{5}) \\{([^\\}]+)\\}\\[([^\\[]+)\\] (.*)");
    }

    public void main() throws Exception {
        while (!file.exists() && !exit) {
            Thread.sleep(500);
        }
        if (exit) {
            return;
        }
        FileInputStream fis = new FileInputStream(file);
        BufferedReader in = new BufferedReader(new InputStreamReader(fis, "UTF8"));
        FileChannel channel = fis.getChannel();
        if (skip) {
            channel.position(channel.size());
        }
        long i = 1;
        String line;
        while (startLine > 0 && i < startLine && ((line = in.readLine()) != null)) {
            Matcher m = pattern.matcher(line);
            if (m.find()) {
                i++;
            }
        }
        long length = 0;
        while (!exit) {
            Message msg = null;
            while ((line = in.readLine()) != null) {
                Matcher m = pattern.matcher(line);
                if (m.find()) {
                    handleEvent(msg);
                    msg = new Message(i++);
                    msg.setDate(Message.format.parse(m.group(1).trim()));
                    msg.setLevel(Level.valueOf(m.group(2).trim()));
                    msg.setThread(m.group(3).trim());
                    msg.setClazz(m.group(4).trim());
                    msg.setMessage(m.group(5).trim());
                } else {
                    if (msg != null) {
                        msg.getException().append(line);
                        msg.getException().append("\n");
                    }
                }
            }
            handleEvent(msg);
            long pos = channel.position();
            if (length > pos) {
                channel.position(pos);
                msg = new Message(i++);
                msg.setLevel(Level.INFO);
                msg.setDate(new Date());
                msg.setMessage("File truncated");
                msg.setThread("");
                msg.setClazz("");
                handleEvent(msg);
            } else if (length == pos) {
                Thread.sleep(500);
            }
            length = pos;
        }
        fis.close();
        exit = false;
    }

    @Override
    public void run() {
        try {
            main();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void cropFile() {
        if (file == null || !file.exists()) {
            return;
        }
        exit();
        try {
            FileWriter w = new FileWriter(file);
            w.write(new char[0]);
            w.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        while (exit)
            ;
        new Thread(this).start();
    }

    public void exit() {
        this.exit = true;
    }

    private void handleEvent(Message message) {
        for (MessageListener listener : this.listeners) {
            listener.update(message);
        }
    }

    /**
     * @param listener
     *            the listener to set
     */
    public void addMessageListener(MessageListener listener) {
        this.listeners.add(listener);
    }

    /**
     * @param file
     *            the file to set
     */
    public void setFile(File file) {
        this.file = file;
    }

    /**
     * @param skip
     *            the skip to set
     */
    public void setSkip(boolean skip) {
        this.skip = skip;
    }

    public void setStartLine(long startLine) {
        this.startLine = startLine;
    }
}